﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace NDF.XnaLibrary
{
	public class Map
	{
		private VertexPositionNormalTexture[] mVertices;
		private List<int> mIndices;

		private VertexBuffer mVertexBuffer;
		private IndexBuffer mIndexBuffer;
		
		public Map(int terrainLength, int terrainWidth, int mapUnitSize)
		{
			this.TerrainLength = terrainLength;
			this.TerrainWidth = terrainWidth;
			this.MapUnitSize = mapUnitSize;

			mVertices = new VertexPositionNormalTexture[TerrainLength * TerrainWidth];
			mIndices = new List<int>();

			if (Current == null)
				Current = this;
		}

		#region Properties

		/// <summary>
		/// Gets the width of the map.
		/// </summary>
		public int TerrainWidth { get; private set; }

		/// <summary>
		/// Gets the length of the map.
		/// </summary>
		public int TerrainLength { get; private set; }

		/// <summary>
		/// Gets the distance between two neighbouring vertices.
		/// </summary>
		public float MapUnitSize { get; private set; }

		/// <summary>
		/// Gets the array containing all the vertices of the map
		/// </summary>
		public VertexPositionNormalTexture[] Vertices
		{ 
			get { return mVertices; } 
		}

		/// <summary>
		/// Gets the list containing all the indices of the map.
		/// </summary>
		public List<int> Indices
		{
			get { return mIndices; }
		}

		/// <summary>
		/// Gets or sets the currently active map.
		/// </summary>
		public static Map Current { get; set; }

		#endregion

		#region Methods

		public void GenerateBasicMap()
		{
			float counter = 0;
			bool incrementing = true;
			Random random = new Random();
			for (int x = 0; x < TerrainLength; x++)
			{
				for (int z = 0; z < TerrainWidth; z++)
				{
					VertexPositionNormalTexture vertex = new VertexPositionNormalTexture(new Vector3(x,0,z), Vector3.Zero, Vector2.Zero);
					vertex.TextureCoordinate.X = (float) x / 30f;
					vertex.TextureCoordinate.Y = (float) z / 30f;
					mVertices[x + z * TerrainLength] = vertex;
					

					if (incrementing)
						counter++;
					else 
						counter--;

					if (Math.Abs(counter - 5) < 0.5 || Math.Abs(counter) < 0.5)
						incrementing = !incrementing;
				}
			}

			for (int x = 0; x < TerrainLength-1; x++)
			{
				for (int z = 0; z < TerrainWidth-1; z++)
				{
					int topLeft = x + z * TerrainLength;
					int bottomLeft = x + (z + 1) * TerrainLength;
					int bottomRight = x + 1 + (z + 1) * TerrainLength;
					int topRight = x + 1 + z * TerrainLength;

					mIndices.Add(topLeft);
					mIndices.Add(bottomLeft);
					mIndices.Add(bottomRight);

					mIndices.Add(topLeft);
					mIndices.Add(bottomRight);
					mIndices.Add(topRight);

					GenerateTriangleNormals(topLeft, bottomRight, bottomLeft);
					GenerateTriangleNormals(topLeft, topRight, bottomRight);

				}
			}

			foreach (VertexPositionNormalTexture vertex in mVertices)
			{
				vertex.Normal.Normalize();
			}
		}

		public void LoadFromHeightMap(Texture2D heightMap)
		{
			TerrainLength = heightMap.Bounds.Width;
			TerrainWidth = heightMap.Bounds.Height;

			mVertices = new VertexPositionNormalTexture[TerrainLength * TerrainWidth];
			mIndices = new List<int>();

			Color[] vertexHeights = new Color[TerrainLength * TerrainWidth];
			heightMap.GetData<Color>(vertexHeights);

			for (int x = 0; x < TerrainWidth; x++)
			{
				for (int z = 0; z < TerrainLength; z++)
				{
					VertexPositionNormalTexture vertex = new VertexPositionNormalTexture(new Vector3(x, vertexHeights[x + z*TerrainLength].R, z), Vector3.Zero, Vector2.Zero);
					vertex.TextureCoordinate.X = x / 30f;
					vertex.TextureCoordinate.Y = z / 30f;
					mVertices[x + z * TerrainLength] = vertex;
				}
			}

			for (int x = 0; x < TerrainWidth - 1; x++)
			{
				for (int z = 0; z < TerrainLength - 1; z++)
				{
					int topLeft = x + z * TerrainLength;
					int bottomLeft = x + (z + 1) * TerrainLength;
					int bottomRight = x + 1 + (z + 1) * TerrainLength;
					int topRight = x + 1 + z * TerrainLength;

					mIndices.Add(topLeft);
					mIndices.Add(bottomLeft);
					mIndices.Add(bottomRight);

					mIndices.Add(topLeft);
					mIndices.Add(bottomRight);
					mIndices.Add(topRight);

					GenerateTriangleNormals(topLeft, bottomRight, bottomLeft);
					GenerateTriangleNormals(topLeft, topRight, bottomRight);

				}
			}

			foreach (VertexPositionNormalTexture vertex in mVertices)
			{
				vertex.Normal.Normalize();
			}
		}

		private void GenerateTriangleNormals(int a, int b, int c)
		{
			Vector3 side1 = mVertices[a].Position - mVertices[c].Position;
			Vector3 side2 = mVertices[a].Position - mVertices[b].Position;
			Vector3 vertexNormal = Vector3.Cross(side1, side2);
			vertexNormal.Normalize();
			mVertices[a].Normal += vertexNormal;
			mVertices[c].Normal += vertexNormal;
			mVertices[b].Normal += vertexNormal;
		}

		public void Draw(Effect effect)
		{
			foreach (EffectPass pass in effect.CurrentTechnique.Passes)
			{
				pass.Apply();

				effect.GraphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, mVertices, 0, mVertices.Length,
					mIndices.ToArray(), 0, (mIndices.Count)/3, VertexPositionNormalTexture.VertexDeclaration);
			}
		}

		#endregion
	}
}
